/*
* Copyright 2011 Pavel Kozhin 
*
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
* 
* http://www.apache.org/licenses/LICENSE-2.0
* 
* Unless required by applicable law or agreed to in writing, software 
* distributed  under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License.
*
* Author: Pavel Kozhin.
*/
package org.etherframework.wire
{
	import flash.errors.IllegalOperationError;
	import flash.utils.Dictionary;
	
	import mx.core.IFactory;
	
	import org.etherframework.common.wire.IMetaProcessor;
	import org.etherframework.common.wire.IWire;
	import org.etherframework.common.wire.IWiring;
	import org.etherframework.core.$Globals;
	import org.etherframework.core.GlobalConfig;
	import org.etherframework.core.bridge.IBridge;
	import org.etherframework.core.bridge.IBridgeFacades;
	import org.etherframework.core.bridge.IObjectController;
	import org.etherframework.core.context.Context;
	import org.etherframework.core.context.IContexts;
	import org.etherframework.core.context.IContextual;
	import org.etherframework.core.context.ISingleContextual;
	import org.etherframework.core.module.ModulePart;
	import org.etherframework.core.module.ModulePartFactory;
	import org.etherframework.core.reflection.Metatag;
	import org.etherframework.core.utils.ClassUtils;
	import org.etherframework.wire.metadata.MetadataManager;
	
	public class Wiring extends ModulePart implements IWiring, ISingleContextual
	{
		private static var _config:GlobalConfig;
		private static var _globalProcessors:Vector.<ProcessorFactory>;
		private static var _metadataManager:MetadataManager;
		private static var _intrinsicMetatags:Vector.<String>;
		
		private var _globalProcessorsByName:Dictionary = new Dictionary();
		private var _intrinsicMetatagNames:Dictionary = new Dictionary();
		private var _processors:Dictionary = new Dictionary();
		private var _facade:IBridgeFacades;
		private var _contexts:IContexts;
		private var _owner:IWiring;
		private var _context:Context;
		
		public static function initialize(processors:Vector.<ProcessorFactory> = null, 
										  intrinsicMetatags:Vector.<String> = null):void
		{
			if ($Globals.config != _config)
			{
				_config = $Globals.config;
				
				const factory:IFactory = _config.superFactory.createFactory(MetadataManager);
				_metadataManager = MetadataManager(factory.newInstance());
				_intrinsicMetatags = intrinsicMetatags;
				_globalProcessors = processors;
				
				$Globals.config.addObjectProcessor(
					new ObjectProcessor(_metadataManager, intrinsicMetatags));
				
				$Globals.config.wiringFactory = 
					new ModulePartFactory(Wiring);
			}
		}
		
		public function Wiring(owner:IWiring, context:Context)
		{
			if (!context)
				throw new ArgumentError("Argument context:Context can not be null.");
			
			_owner = owner;
			_context = context;
		}
		
		override public function init(bridge:IBridge):void
		{
			super.init(bridge);
			
			_facade = bridge.facade;
			_contexts = bridge.contexts;
			
			initPart();
		}
		
		public function hasWire(target:Object):IWire
		{
			if (!target)
				throw new ArgumentError("Argument target:Object can not be null.");
			
			const controller:IObjectController = bridge.hasController(target);
			
			return controller ? controller.wire : null;
		}
		
		public function createWire(target:Object):IWire
		{
			if (!target)
				throw new ArgumentError("Argument target:Object can not be null.");
			
			const controller:IObjectController = bridge.hasController(target);
			
			if (controller && controller.wire)
				throw new IllegalOperationError("Object " + target + " is already wired.");
			
			var result:Wire;
			var processor:IMetaProcessor;
			const metatags:Vector.<Metatag> = 
				_metadataManager.getMetadataInfo(target).metatags;
			
			for each (var metatag:Metatag in metatags)
			{
				if (_intrinsicMetatagNames[metatag.name])
					continue;
				
				processor = _facade.contextual(_context).createProcessor(metatag.name);
				
				if (!processor)
					throw new Error("IProcessor for metatag " + metatag.name + " does not exist. " +
						"Make sure that it is in enabledProcessors of EtherConfig or added manually in IContext.");
					
				if (!result)
					result = new Wire(target);
				
				result.addProcessor(processor, metatag.allMembers);
			}
			
			return result;
		}
		
		public function createProcessor(metatag:String):IMetaProcessor
		{
			if (_processors[metatag])
			{
				return ProcessorFactory(_processors[metatag]).getInstance();
			}
			else if (_owner)
			{
				return _owner.createProcessor(metatag);
			}
			else if (_globalProcessorsByName[metatag])
			{
				return ProcessorFactory(_globalProcessorsByName[metatag]).getInstance();
			}
			
			return null;
		}
		
		public function addProcessor(factory:ProcessorFactory):void
		{
			if (!factory)
				throw new ArgumentError("Argument factory:ProcessorFactory can not be empty.");
			if (!ClassUtils.implementsInterface(factory.generator, IMetaProcessor))
				throw new ArgumentError("Class " + factory.generator + 
					" must implement org.etherframework.wire.processors::IProcessor interface.");
			if (_processors[factory.name])
				throw new ArgumentError("IMetaProcessor for metatag " + factory.name + " is already added.");
			
			_processors[factory.name] = factory;
		}
		
		public function hasProcessor(metatag:String):Class
		{
			if (!metatag)
				throw new ArgumentError("Argument metatag:String can not be empty.");
			
			return _processors[metatag] ? ProcessorFactory(_processors[metatag]).generator as Class : null;
		}
		
		public function assignProcessors(instanceOrClass:Object, processors:Vector.<IMetaProcessor>):void
		{
			throw new IllegalOperationError("Method is not implemented.");
		}
		
		override public function destroy():void
		{
			_contexts.dispose(this);
		}
		
		public function contextual(context:Context):IWiring
		{
			return _contexts.contextual(context, IWiring, this) as IWiring;
		}
		
		public function create(context:Context, type:Class):IContextual
		{
			var result:Wiring = new Wiring(this, context);
			result.init(bridge);
			return result;
		}
		
		public function dispose():void
		{
			_facade = null;
			
			for (var item:String in _processors)
			{
				delete _processors[item];
			}
		}
		
		public function get context():Context { return _context; }
		
		private function initPart():void
		{
			var i:int;
			const intrinsicMetatags:Vector.<String> = 
				_intrinsicMetatags  || Vector.<String>([]);
			
			if (!_metadataManager.initialized)
			{
				const tags:Vector.<String> = new Vector.<String>();
				
				if (_globalProcessors)
				{
					for (i = 0; i < _globalProcessors.length; i++) 
					{
						tags.push(_globalProcessors[i].name);
						_globalProcessorsByName[_globalProcessors[i].name] = _globalProcessors[i];
					}
				}
				
				_metadataManager.initialize(tags.concat(intrinsicMetatags));
			}
			
			for (i = 0; i < intrinsicMetatags.length; i++) 
			{
				_intrinsicMetatagNames[intrinsicMetatags[i]] = true;
			}
		}
	}
}